<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <style>
        .grad1 {
            background: linear-gradient(to right, orange, red);
        }
        .grad2 {
            background: linear-gradient(to right, orange, green);
        }
        .outer{
            border-radius: 6px;
            background-color: darkgray;
            height: 13px;
            width: 300px;
        }
        .inner{
            border-radius: 6px;
            background-color: #891627;
            height: 13px;
            width: 0;
        }
        .buttonUp{
            background: #e4b108;
            padding: 10px 20px;
            border-radius: 5px;
            margin-bottom: 20px;
        }
        #fileProgress,#fileComplete{
            font-size: 12px;
            flex: 0 0 400px;
            background-color: #d6eadf;
            padding: 20px;
            border-radius: 20px;
            color: #888888;
        }
    </style>
</head>
<body>

<input hidden type='file' multiple name='filesList' id='fileInput' >

<button class="buttonUp" onclick="openUpload()">点击上传</button>

<div style="display: flex">
    <div id="fileProgress" >
        <div style="color: #e7761b;font-size: 16px">请选择上传文件</div>
    </div>
    <div style="width: 50px;"></div>
    <div id="fileComplete"  >
        <div style="color: #17c620;font-size: 16px">已上传文件</div>
    </div>
</div>



<script src="/js/spark-md5.main.js"></script>
<script src="/js/jq.js"></script>
<script>
    let chunkSize = 1 * 1024 * 1024
    let filesList = []
    let errUploadFile = {}
    let successUploadFile = {}
    let reqXhr = {}
    let progress ={}

    function openUpload() {
        clearData()
        $("#fileInput").click()
    }
    function clearData() {
        errUploadFile = {}
        reqXhr = {}
        progress={}
        $('#fileInput').val(''); //清空已选择的file缓存
    }
    $("#fileInput").change(function (data) {
        console.log(filesList,"添加前"  )
        for (let i = 0; i < data.target.files.length; i++) {
            let file = data.target.files[i]
            let uuid = UUID()
            let obj = {
                [uuid]:file,
            }
            filesList.push(obj)
            fileDom(file,uuid)
        }
        console.log(filesList,"添加后"  )

    })


    function fileDom(file,uuid){
                    $("#fileProgress").append(`
                        <div style="padding: 10px 0" class="waper-${uuid}">
                            <div>文件名称：${file.name}</div>
                            <div  style="display: flex;align-items: center;justify-content: space-between;">
                                <div class="outer" >
                                    <div class="inner grad1" ></div>
                                </div>
                                <span class="rate">0%</span>
                                <input id="enterUpload-${uuid}" type="button"  onclick="startUpload('${uuid}')" value="上传">
                                <input style="display: none" id="cancelUpload-${uuid}" type="button"  onclick="cancelUpload('${uuid}')" value="取消">
                                <input style="display: none" id="continueUpload-${uuid}" type="button"  onclick="continueUpload('${uuid}')" value="继续上传">
                            </div>
                            <div style="padding: 0 0 10px 0">
                                 已上传: <span id="completeSize-${uuid}">0</span>KB
                                 <span style="padding: 0 10px"></span>
                                 总大小:  <span >${getSizeMB(file.size)}</span>
                            </div>
                        </div>
                    `)

    }
    function check(md5,userId){
        return new Promise(resolve => {
            let req ={
                md5:md5,
                userId:userId,
            }
            $.ajax({
                type: "POST",
                url: '/file/checkMd5',
                data: req,
                //dataType: "json",

                success: function (res) {
                    resolve(res.data)

                },
                error: function (res) {
                }
            })
        })
    }
    async function startUpload(uuid) {

        $(`#cancelUpload-${uuid}`).show();
        $(`#enterUpload-${uuid}`).hide();
        let file = getFileByUUID(uuid)
        // 获取文件的总分片数
        const chunks = Math.ceil(file.size / chunkSize)
        // 取两个md5值作为整体文件的唯一标识
        let fileMd5 =await getFileMd5(file)

        let userId = localStorage.getItem("userId")
        if (!userId){
            alert("请在localStorage存储一个  4位数值类型 的 userId ")
            return  false;
        }


        progress[uuid] = {}
        reqXhr[uuid] = []
        errUploadFile[uuid] = []
        successUploadFile[uuid] = []


        // 优先判断文件是否在数据库中存在，存在则秒传
        let fileData= await check(fileMd5,userId)
        if (fileData != null){
            completeDom( fileData,uuid )
            return false;
        }

        // 文件如果不存在，则继续走下去进行上传
        let formData;
        if (file.size > 1 * 1024 * 1024) {
            for(let index = 0; index < chunks; index++) {
            // 走分片上传
                formData = new FormData();
                let startIndex = index * chunkSize;
                let endIndex = (index + 1) * chunkSize
                let chunkFile = file.slice(startIndex, endIndex)
                // 分片总数
                formData.append("chunks", chunks);
                // 当前分片数
                formData.append("chunkIndex", index);
                // 分片大小
                formData.append("chunkSize", chunkFile.size);
                // 文件类型
                formData.append('type', file.type)
                // 文件总大小
                formData.append("size", file.size);
                // 文件名
                formData.append("name", file.name);
                // 整个文件的id值，及md5值
                formData.append("fileMd5", fileMd5);
                // 用户id
                formData.append("createUserId", userId);
                // 计算当前文件分片的md5值
                let currentChunkMd5 = await getMD5(chunkFile);
                formData.append("currentChunkMd5", currentChunkMd5);
                formData.append("file", chunkFile);
                formData.append("chunkFlag", true);
                xhrSend(formData,uuid)
            }
        }else {
        // 整体上传
            formData = new FormData();
            // 文件类型
            formData.append('type', file.type)
            // 文件总大小
            formData.append("size", file.size);
            // 文件名
            formData.append("name", file.name);
            // 整个文件的id值，及md5值
            formData.append("fileMd5", fileMd5);
            // 用户id
            formData.append("createUserId", userId);

            formData.append("file", file);
            formData.append("chunkFlag", false);
            xhrSend(formData,uuid)
        }


    }


    // 发送分片请求
    function xhrSend(formData,uuid) {

        let chunkFlag = formData.get("chunkFlag")
        let index;
        if (chunkFlag == "true"){//如果是分片上传
            index = formData.get("chunkIndex")
        }else{//不是分片上传
            index = 0
        }

        progress[uuid][index] = {
            loaded:0
        }


        let  xhrs =$.ajax({
            type: "POST",
            url: '/file/upload',
            data: formData,
            contentType: false, //必须
            processData: false, //必须
            //dataType: "json",
            xhr: function () {
                let xhr = new XMLHttpRequest();
                let total = getFileByUUID(uuid).size;
                xhr.upload.addEventListener('progress', function (e) {
                    progress[uuid][index].loaded = e.loaded

                    // 累加每个分片的下载量和需要下载的总量
                    let loaded = 0
                    for (const indexKey in progress[uuid]) {
                        loaded += progress[uuid][indexKey].loaded
                    }
                    let per = loaded > total ? 1 : loaded / total
                    $(`#completeSize-${uuid}`).html((loaded/1024).toFixed(0))
                    $(`.waper-${uuid}  .inner`).css("width", per * 300 + "px");
                    $(`.waper-${uuid}  .rate`).html(Math.floor(per * 100) + '%');
                })
                return xhr;
            },
            success: function (res) {
                delete errUploadFile[uuid]  //失败的文件会插入errUploadFile中，所以上传成功后需要删除掉
                let fileData = res.data;
                if (fileData != null){
                    // fileData有值代表分片或整体上传已经成功
                    completeDom( fileData,uuid )
                }


                console.log("上传成功");
            },
            error: function (res) {
                errUploadFile[uuid].push(formData)//将上传失败的文件插入到errUploadFile集合中，用于重新上传
                console.log("上传失败");
            }
        })
        reqXhr[uuid].push(xhrs)//用于xhr示例放入集合中，后续会使用集合中的示例进行取消正在上传的文件
    }
    // 渲染上传完成的文件dom
    function completeDom(fileData,uuid){
            // console.log(uuid,"====uuid==="  )
            //
            // let file = getFileByUUID(uuid);

            $("#fileComplete").append(`
                        <div style="padding: 10px 0">
                            <div>文件名称：${fileData.fileName}</div>
                            <div  style="display: flex;align-items: center;justify-content: space-between;">
                                <div class="outer" >
                                    <div class="inner grad2" style="width: 300px"></div>
                                </div>
                                <span class="rate">100%</span>
                                <span style="color: green">已完成</span>
                            </div>
                            <div>文件大小：${getSizeMB(fileData.fileSize)}</div>
                        </div>
            `)
            let deleteIndex = getFileIndexByUUID(uuid) //获取删除下标
            filesList.splice(deleteIndex,1) //删除文件集合中已上传的文件
            $(`.waper-${uuid}`).remove(); //删除dom集合中已上传的文件
    }
    // 继续上传
    async function continueUpload(uuid) {
        $(`#cancelUpload-${uuid}`).show();
        $(`#continueUpload-${uuid}`).hide();

        errUploadFile[uuid].forEach((formData,index)=>{
            xhrSend(formData,uuid)
        })
    }
    // 取消上传
    async function cancelUpload(uuid) {
         $(`#cancelUpload-${uuid}`).hide();
         $(`#continueUpload-${uuid}`).show();


        // let file = filesList[fileIndex].file
        //
        // // 取两个md5值作为整体文件的唯一标识
        // let fileMd5 =await getFileMd5(file)

        reqXhr[uuid].forEach(x=>{
            x.abort()
        })

    }
    // 根据UUID获取文件
    function getFileByUUID(uuid) {
        let index = filesList.findIndex(item=>{
            let key = Object.keys(item)[0]
            return key == uuid
        })
        return filesList[index][uuid]
    }
    // 根据UUID获取文件下标
    function getFileIndexByUUID(uuid) {
        let index = filesList.findIndex(item=>{
            let key = Object.keys(item)[0]
            return key == uuid
        })
        return index
    }
    //根据文件大小，转换成MB或者KB
    function getSizeMB(fileSize){
        let fileSizeMB = fileSize / 1024 / 1024
        let size ;
        if (fileSizeMB > 1){
            size = fileSizeMB.toFixed(2) + "MB"
        }else{
            size = (fileSize / 1024).toFixed(0) + "KB"
        }
        return size
    }
    // 获取文件的md5
    async function getFileMd5(file){
        const chunks = Math.ceil(file.size / chunkSize)
        let fileMd5 = ''
        if (chunks >= 2) {
            let startMd5 = await getMD5(file.slice(0, 1 * chunkSize))
            let endMd5 = await getMD5(file.slice((chunks-1) * chunkSize, chunks * chunkSize))
            fileMd5 = startMd5 + endMd5;
        } else {
            fileMd5 = await getMD5(file)
        }
        return fileMd5;
    }
    // 生成md5
    const getMD5 = (file) => {
        return new Promise((resove, reject) => {
            // 使用sparkMD5的ArrayBuffer类，读取二进制文件
            const spark = new SparkMD5.ArrayBuffer()
            const fileReader = new FileReader()
            // 异步操作，读完后的结果
            fileReader.onload = (e) => {
                // 把文件开始传入spark
                spark.append(e.target.result)
                // spark计算出MD5后的结果
                const _md5 = spark.end()
                resove(_md5)
                // 下面可以写一些自己需要的业务代码, 例如 fileItem.fileMD5 = _md5
            }
            // fileReader读取二进制文件
            fileReader.readAsArrayBuffer(file)
        })
    }
    // 生成 通用唯一识别码 UUID
    function UUID() {
        return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
            var r = Math.random() * 16 | 0,
                v = c == 'x' ? r : (r & 0x3 | 0x8);
            return v.toString(16);
        });
    }
</script>
</body>
</html>